package cn.ycc1.boot3redisson.config;

import cn.ycc1.boot3redisson.annotation.MyRedissonLock;
import cn.ycc1.boot3redisson.util.SpELUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.cache.interceptor.KeyGenerator;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 实现AOP
 * @author ycc
 * @date 2024/7/31
 */
@Component
@Aspect
@Slf4j
public class RedissonLockAspect implements ApplicationContextAware {
    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(distributedLock)")
    public Object lock(ProceedingJoinPoint proceedingJoinPoint, MyRedissonLock distributedLock) {
        String key = distributedLock.key();//key
        String keyGenerator = distributedLock.keyGenerator();//自定义的keyGenerator
        String cacheName = distributedLock.cacheName();//缓存空间
        long waitTime = distributedLock.waitTime(); //最大等待时间 毫秒
        long expire = distributedLock.expire(); //失效时间(防止线程意外无法释放锁) 毫秒
        waitTime = waitTime < 0 ? 0 : waitTime;
        expire = expire < 0 ? 0 : expire;

        StringBuffer rKey = new StringBuffer();
        try {
            Signature signature = proceedingJoinPoint.getSignature();
            String className = proceedingJoinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            Object[] objects = proceedingJoinPoint.getArgs();
            Class[] parameterTypes = new Class[objects.length];
            /**
             * 打印输入的参数
             */
            for (int i = 0; i < objects.length; i++) {
                Object obj = objects[i];
                // System.out.println("type:" + obj.getClass().getSimpleName() + ",   value:" + obj);
                parameterTypes[i] = obj.getClass();
            }

            Method method = signature.getDeclaringType().getMethod(methodName, parameterTypes);
            Parameter[] parameters = method.getParameters();
//            for (Parameter parameter : parameters) {
//                System.out.println(parameter.getName() + "," + parameter.getType() + ",");
//            }

            if (!"".equals(cacheName)) {
                rKey.append(cacheName).append("::");
            }

            if (!key.equals("")) {
                //利用spel表达式转换key
                Map<String, Object> variables = new HashMap<>();
                for (int i = 0; i < parameters.length; i++) {
                    variables.put(parameters[i].getName(), objects[i]);
                }
                rKey.append(SpELUtil.parse(key, variables, String.class));
            } else if (!keyGenerator.equals("")) {
                //未设置key,则按照默认规则生成key
                KeyGenerator kg = applicationContext.getBean(keyGenerator, KeyGenerator.class);
                rKey.append(kg.generate(proceedingJoinPoint.getTarget(), method, objects).toString());
            } else {//如果未设置key,也没有使用keyGenerator,则使用默认的key生成器
                KeyGenerator kg = applicationContext.getBean(MyRedissonKeyGenerator.myKeyGeneratorByMethodName, KeyGenerator.class);
                rKey.append(kg.generate(proceedingJoinPoint.getTarget(), method, objects).toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("运行时异常",e);
        }
//        rKey.append("my:redisson:lock2");
        RLock lock = redissonClient.getLock(rKey.toString());
        boolean hasLock = false;
        try {
            hasLock = lock.tryLock(waitTime, expire, TimeUnit.MILLISECONDS);
            if (hasLock) {
                log.info("获取分布式锁成功，key={}", rKey);
                return proceedingJoinPoint.proceed();
            } else {
                log.info("获取锁失败");
            }
        } catch (Throwable e) {
            log.error("切面分布式锁异常:{}", e);
        } finally {
            if (hasLock) {
                lock.unlock();
                log.info("解锁成功:{}", rKey);
            }
        }
        return null;
    }

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

